#ifndef UNIAST_RUNTIME_SYMBOL_TABLE_H
#define UNIAST_RUNTIME_SYMBOL_TABLE_H
#include "symbolTable/Variable.h"
#include "type/Type.h"
#include <map>
#include <memory>
#include <stack>
#include <string>

namespace UniAST
{
class Scope;
template <typename T> class SymbolTable
{
  private:
    std::map<std::string, std::shared_ptr<T>> symbolTable;

    Scope *belongTo;

  public:
    SymbolTable(Scope *belongTo);

    SymbolTable(const SymbolTable &) = delete;

    SymbolTable &operator=(const SymbolTable &) = delete;

    SymbolTable(SymbolTable &&) = delete;

    ~SymbolTable() = default;

    bool exist(std::string name);

    std::shared_ptr<T> find(std::string name);

    void add(std::string name, std::shared_ptr<T> var);
};

class Scope
{
  public:
    using VariableTable = SymbolTable<Variable>;
    using TypeTable = SymbolTable<UniAST::Type>;

  private:
    // table for variable
    VariableTable variableTable;

    // variable for custom type
    TypeTable typeTable;

    // nullptr if this is global scope
    Scope *parent;

  public:
    Scope(Scope *parent = nullptr);

    Scope(const Scope &) = delete;

    Scope &operator=(const Scope &) = delete;

    Scope(Scope &&) = delete;

    ~Scope() = default;

    Scope *getParentScope();

    // return false if failed
    bool addVariable(std::shared_ptr<Variable> var);

    // return true if name of var in this scope is `name`
    bool existVariable(std::string name);

    // find var from this scope
    // return nullptr if var do not exist.
    std::shared_ptr<Variable> findVariableFromThis(std::string name);

    // return false if failed
    bool addType(std::shared_ptr<UniAST::Type> var);

    // return true if name of type in this scope is `name`
    bool existType(std::string name);

    // find type from this scope
    // return nullptr if type do not exist.
    std::shared_ptr<UniAST::Type> findTypeFromThis(std::string name);
};

class Manager
{
  public:
    // get single instance
    static Manager &getManager();

    Manager(const Manager &) = delete;

    Manager(Manager &&) = delete;

    Manager &operator=(const Manager &) = delete;

    ~Manager() = default;

  private:
    Manager();

    std::stack<std::shared_ptr<Scope>> scopeStack;
    std::shared_ptr<Scope> globalScope;

  public:
    // current handling scope
    std::shared_ptr<Scope> currentScope();
    // create and enter new scope
    void enterScope();
    // exit current scope and enter parent scope
    std::shared_ptr<Scope> exitScope();
};
}; // namespace UniAST

#endif